/*
 * Copyright (c) 2012 Mikaël GUILLEMOT
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.g2de.generators;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.g2de.tools.Tools;

/**
 * Cette classe propose une structure pour la generation de code. L'objectif est
 * d'accelerer le developpement a l'aide de generateurs flexibles
 * 
 * ATTENTION : les marqueurs utilisé doivent etre de la forme "//#TOKEN" sur une
 * ligne. Rien ne doit suivre TOKEN.
 * 
 * La generation necessite donc 2 classes suplementaire : - le generateur
 * (utilisant cette classe) - le template (utilisé comme base pour le
 * generateur).
 * 
 * Le generateur est prevu pour prendre en charge des fichiers java. Le template
 * est prevu pour etre ecrit comme une classe. La directive @SuppressWarnings
 * n'est pas conservé lors du processus de generation. (Astuce : un token peut
 * etre defini si cette directive est indispensable dans le code genere) Pour le
 * moment le generateur ne prend pas en charge l'indentation.
 * 
 * 
 * Note sur l'utillisation : Tout marqueur (Token) est utilisable. C'est au
 * developpeur de le choisir judicieusement pour eviter les conflis. Plusieurs
 * marqueurs peuvent exister sur une meme ligne. Une ligne peut etre definie
 * comme a remplacer a l'aide de la combinaison "//#". Dans ce cas un seul
 * marquer est pris en compte. Une ligne peut etre definie comme a supprimer a
 * l'aide de la combinaison "//-"
 * 
 * Voici un exemple de sequence d'utilisation :
 * 
 * GenerationTools gt = new GenerationTools(); gt.init(...);
 * if(gt.checkToken({...}) { //Do the generation here } gt.close();
 * 
 * @author Mikael GUILLEMOT
 * @version 1.0
 * @since
 * 
 */
public class JGenerationTools {

	private final String GEN_DIRECTIVE = "//#";
	private final String DEL_LINE = "//-";
	private final String SUP_WARN = "@SuppressWarnings";
	private final String BACK_EXT = ".old";

	private BufferedWriter out = null;
	private List<TemplateLine> template = null;
	private String[] tokenTable = null;
	private Tools t;

	// classe interne permetant de representer lune ligne de template, on peu
	// pas heriter de string alors on fait une structure
	/*
	 * on a besoin des info suivantes : le token, la ligne normal (cas de la
	 * date, remplacement simple, peut etre gere de base) le type de ligne :
	 * normal token a remplacer (cas de la date) le token n'est pas precede de
	 * #, si c'est en commentaire le commentaire est conserv�, le token doit
	 * etre indiquer comme reserv� en commentaire. certains token peuvent etre
	 * reserve (par exemple la date). redefinition complete
	 */
	public class TemplateLine {
		/**
		 * ligne de base sans remplacement
		 */
		public static final short NORMAL = -1;
		/**
		 * ligne dont les tokens sont a remplacer
		 */
		public static final short REPLACE = -2;
		// sinon la ligne est remplacé completement

		public String line = null;
		public List<Integer> tokens = null;
		public int typeOrToken = -1;

		public TemplateLine(int token) {
			this.typeOrToken = token;
		}

		public TemplateLine(String line) {
			this.line = line;
		}
	}

	public JGenerationTools() {
		// on suppose que les outils on ete initialisé par la classe appelante
		t = Tools.getInstance();
	}

	/**
	 * le generateur doit appeler cette methode pour commencer la generation,
	 * c'est a faire apres l'initialisation. La generation va appeler les
	 * methodes de remplacement predefinie par le generateur.
	 * 
	 * @param g
	 */
	public void StartGeneration(Generator g) {
		try {
			for (TemplateLine tl : template) {
				if (tl.typeOrToken == TemplateLine.REPLACE)
					for (Integer token : tl.tokens)
						tl.line = g.replaceTokens(tl.line, token);
				else if (tl.typeOrToken >= 0)
					tl.line = g.replaceLine(tl.typeOrToken);
				// au final on ecrit
				out.append(tl.line);
				out.append(Tools.lineSeparator);
			}
		} catch (IOException e) {
			t.logError("IOException : " + e.getMessage());
			e.printStackTrace();
		}

	}

	/**
	 * Cette methode va effectuer plusieurs operations : - verifier la presence
	 * du template - charger le template en memoire - effectuer le netoyage du
	 * template (a savoir supprime les lignes contenant "#-" et les directives
	 * 
	 * @SuppressWarnings) - faire une copie du fichier de destination si il
	 *                    existe et que le parametre backup est vrai - ouvrir le
	 *                    fichier destination (l'ecraser au besoin) on est alors
	 *                    pret a travailler
	 * 
	 * @param destination
	 * @param templateFile
	 * @param tokenList
	 * @param backup
	 * @return
	 * @throws IOException
	 */
	public void init(String destination, String templateFile, String[] tokenList, boolean backup) throws IOException {
		// on verifie que le template est dispo et on le charge
		BufferedReader in = new BufferedReader(new FileReader(templateFile));
		String line = null;
		template = new ArrayList<TemplateLine>();
		File dest = new File(destination);
		String destName = dest.getName().replace(".java", "");// SPECIFIQUE JAVA
		String templateName = new File(templateFile).getName().replace(".java", "");
		tokenTable = tokenList.clone();
		t.log("classe a generer : " + destName);
		// on charge le template en faisant le menage
		while ((line = in.readLine()) != null) {
			line = line.trim();
			// SPECIFIQUE JAVA, ca permet le support de
			// l'edition du template
			// dans un environement de developpement java standard.
			line = line.replaceAll(templateName, destName);
			if (line.contains(SUP_WARN) || line.contains(DEL_LINE) || line.isEmpty())
				;
			else if (line.contains(GEN_DIRECTIVE)) // cas particuier de
													// remplacement de ligne
													// complete
			{
				int tok = -1;
				for (int i = 0; i < tokenList.length; i++) {
					if (line.contains(tokenList[i]))
						if (tok == -1)
							tok = i;
						else {
							tok = -1;
							break;
						}
				}
				if (tok == -1) {
					t.logWarning("Fail to replace line \"" + line + "\"");
					t.logWarning("Token not found or multiple token definition !");
					template.add(new TemplateLine(
							"//FIXME : WARNING, next line hase not been replaced, check if Token is defined correctly! "));
					template.add(new TemplateLine(line));
				} else
					template.add(new TemplateLine(tok));

			} else {
				TemplateLine tl = new TemplateLine(line);
				List<Integer> tokenFound = new ArrayList<Integer>();
				for (int i = 0; i < tokenList.length; i++) {
					// on test les tokens recherché
					if (line.contains(tokenList[i]))
						tokenFound.add(i);
				}
				if (!tokenFound.isEmpty()) {
					tl.typeOrToken = TemplateLine.REPLACE;
					tl.tokens = tokenFound;
				}
				template.add(tl);
			}

		}
		in.close();

		// On a extrai les info on verifie la coherance des token
		for (TemplateLine tl : template)
			if (tl.typeOrToken >= 0)
				tokenTable[tl.typeOrToken] = null;
			else if (tl.typeOrToken == TemplateLine.REPLACE)
				for (Integer i : tl.tokens)
					tokenTable[i] = null;
		for (int i = 0; i < tokenList.length; i++) {
			if (tokenList[i] == null)
				t.logWarning("token " + tokenList[i] + " never used in the template!");
		}

		// on fait notre copie de sauvegarde (on supprime l'ancien backup)
		if (dest.exists() && backup) {
			File bkp = new File(destination + BACK_EXT);
			bkp.delete();
			t.FileCopy(dest, bkp);
		}
		// on cree un nouveau fichier pour nos sources
		dest.delete();
		out = new BufferedWriter(new FileWriter(destination, false));
	}

	/**
	 * renvoi le template (pour analyse eventuelle)
	 * 
	 * @return le template sous forme de liste
	 */
	@Deprecated
	public List<TemplateLine> getTemplate() {
		return template;
	}

	/**
	 * ferme le fichier genere. Termine la phase de generation.
	 */
	public void close() {
		template = null;
		try {
			out.append(Tools.lineSeparator);
			out.close();
			t.log("generation terminee.");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
